//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2020-05-28     ZhangYihua   first version
//
// Description  : 
//################################################################################

module fifo_prefetch #(
parameter           DATA_BW                 = 9,
parameter           RDPL_NUM                = 2,            // read operation pipeline steps, include read address and data pipeline
// rapl:    read address pipeline means pipeline steps from mem_raddr to SRAM, 0 means no DFF between them
// wapl:    write address pipeline means pipeline steps from mem_waddr to SRAM, 0 means no DFF between them
// it's required that rapl must equals to wapl at any case.
// case 1: rapl=wapl=0, normal operation is ok;
// case 2: rapl=wapl=1 with prefetch mode 'RDPL_CACHE', normal operation is ok;
// case 3: rapl=wapl=1 with prefetch mode 'RDPL_SHIFT' or 'RDPL_STUFF', let DEPTH = theory_estimation + 1, and
//                     take afull as back pressure to avoid SRAM to overflow;
// case others: not recommended;
parameter           ARCH_MODE               = "RDPL_SHIFT", // the smallest area, but bubble may exist among read data pipeline(if RDPL_NUM>=2)
//parameter           ARCH_MODE               = "RDPL_STUFF", // no bubble exist, but memory must support individul CE for each RDPL step
//parameter           ARCH_MODE               = "RDPL_CACHE", // no bubble exist, but more cache REGs comsumed, about (RDPL_NUM+2)*DATA_BW

// the following parameters are calculated automatically
parameter           CNT_BW                  = (ARCH_MODE=="RDPL_CACHE") ? $clog2(RDPL_NUM+3) :
                                                                          $clog2(RDPL_NUM+1)
) ( 
input                                       rst_n,
input                                       clk,

// raw fifo with read data latency > 0
input                                       raw_fifo_empty,
output                                      raw_fifo_ren,

// zero read latency fifo after prefetching
input                                       zrl_fifo_ren,
output                                      zrl_fifo_empty,
output              [DATA_BW-1:0]           zrl_fifo_rdata,
output                                      zrl_fifo_ruf,   // read underflow

// clock enable for memory read data pipeline 
// RDPL_SHIFT: shift and hold all RDPL REGs by mem_rdpl_ce[0]
// RDPL_STUFF: shift and hold each step of RDPL REGs by individual mem_rdpl_ce[x]
// RDPL_CACHE: mem_rdpl_ce[0] is just for clock gating only, it has nothing to with functionality
output              [RDPL_NUM-1:0]          mem_rdpl_ce,
input               [DATA_BW-1:0]           mem_rdata,

output              [CNT_BW-1:0]            prefetch_cnt
);

//################################################################################
// define local varialbe and localparam
//################################################################################


//################################################################################
// main
//################################################################################

assign zrl_fifo_ruf = zrl_fifo_ren & zrl_fifo_empty;

generate if (ARCH_MODE=="RDPL_SHIFT") begin:G_SHIFT

    wire                [RDPL_NUM+1-1:0]        empty_v;
    reg                 [RDPL_NUM-1:0]          empty_d;
    wire                [RDPL_NUM-1:0]          vld_d;
    wire                                        all_empty;
    wire                                        rdpl_ce;
    wire                                        vld_nc;

    assign empty_v   = {empty_d, raw_fifo_empty};
    assign all_empty = &empty_v[0+:RDPL_NUM];

    assign rdpl_ce = zrl_fifo_ren | (zrl_fifo_empty & (~all_empty));

    always@(posedge clk or negedge rst_n) begin
        if (rst_n==1'b0) begin
            empty_d <=`U_DLY {RDPL_NUM{1'b1}};
        end else begin
            if (rdpl_ce==1'b1)
                empty_d <=`U_DLY empty_v[0+:RDPL_NUM];
            else
                ;
        end
    end
    assign raw_fifo_ren = (zrl_fifo_empty | zrl_fifo_ren) & (~raw_fifo_empty);
    assign mem_rdpl_ce  = {RDPL_NUM{rdpl_ce}};

    assign zrl_fifo_empty = empty_v[RDPL_NUM];
    assign zrl_fifo_rdata = mem_rdata;
    
    assign vld_d = ~empty_d;
    add_all #(.OPRD_BW(1), .OPRD_NUM(RDPL_NUM)) u_cnt (.rst_n(rst_n), .clk(clk), .cke(1'b1), .vld_in(1'b1), .oprds(vld_d), .vld_out(vld_nc), .sum_o(prefetch_cnt));

end else if (ARCH_MODE=="RDPL_STUFF") begin:G_STUFF

    wire                [RDPL_NUM+1-1:0]        empty_v;
    wire                [RDPL_NUM-1:0]          both_empty;
    reg                 [RDPL_NUM-1:0]          exist_empty;
    reg                 [RDPL_NUM-1:0]          rdpl_ce;
    reg                 [RDPL_NUM-1:0]          empty_d;
    wire                [RDPL_NUM-1:0]          vld_d;
    wire                                        vld_nc;

    assign empty_v    = {empty_d, raw_fifo_empty};
    assign both_empty = empty_v[1+:RDPL_NUM] & empty_v[0+:RDPL_NUM];

    always@(*) begin:CE
        integer         i;

        exist_empty[RDPL_NUM-1] = empty_v[RDPL_NUM];
        for (i=RDPL_NUM-1; i>0; i=i-1) begin
            exist_empty[i-1] = exist_empty[i] | empty_v[i];
        end

        for (i=0; i<RDPL_NUM; i=i+1) begin
             rdpl_ce[i] = (exist_empty[i] | zrl_fifo_ren) & (~both_empty[i]);
        end
    end

    genvar g;
    for (g=0; g<RDPL_NUM; g=g+1) begin:G_RDPL
        
        always@(posedge clk or negedge rst_n) begin
            if (rst_n==1'b0) begin
                empty_d[g] <=`U_DLY 1'b1;
            end else begin
                if (rdpl_ce[g]==1'b1)
                    empty_d[g] <=`U_DLY empty_v[g];
                else
                    ;
            end
        end

    end

    assign raw_fifo_ren   = (exist_empty[0] | zrl_fifo_ren) & (~raw_fifo_empty);
    assign mem_rdpl_ce    = rdpl_ce;

    assign zrl_fifo_empty = empty_v[RDPL_NUM];
    assign zrl_fifo_rdata = mem_rdata;

    assign vld_d = ~empty_d;
    add_all #(.OPRD_BW(1), .OPRD_NUM(RDPL_NUM)) u_cnt (.rst_n(rst_n), .clk(clk), .cke(1'b1), .vld_in(1'b1), .oprds(vld_d), .vld_out(vld_nc), .sum_o(prefetch_cnt));

end else if (ARCH_MODE=="RDPL_CACHE") begin:G_CACHE

    localparam          DEPTH                   = RDPL_NUM+2;
    localparam          PTR_BW                  = $clog2(DEPTH);
    localparam          DEPTH_EXP               = 1<<PTR_BW;
    localparam  [CNT_BW-1:0]    DEPTH_V         = DEPTH;
    localparam  [CNT_BW-1:0]    POS_ONE         = 1;
    localparam  [CNT_BW-1:0]    NEG_ONE         = {CNT_BW{1'b1}};
    localparam  [CNT_BW-1:0]    ZERO            = 0;
    localparam  [CNT_BW-1:0]    DEPTH_DEC       = DEPTH-1;

    wire                                        cache_ren;
    wire                                        wcnt_ce;
    wire                [CNT_BW-1:0]            wcnt_inc;
    reg                 [CNT_BW-1:0]            wcnt;
    wire                [CNT_BW-1:0]            wptr;
    reg                 [RDPL_NUM+1-1:0]        rvld_d;
    reg                 [CNT_BW-1:0]            wptr_d[RDPL_NUM+1-1:0];
    wire                                        wptr_chg;
    wire                                        rdpl_ce;
    wire                [CNT_BW-1:0]            rptr_c;
    reg                 [CNT_BW-1:0]            rptr;
    wire                                        mem_rvld_nc;

    assign raw_fifo_ren = (wcnt<DEPTH_V) ? (~raw_fifo_empty) : 1'b0;
    assign cache_ren    = zrl_fifo_ren & (~zrl_fifo_empty);

    assign wcnt_ce  = cache_ren ^ raw_fifo_ren;
    assign wcnt_inc = (raw_fifo_ren==1'b1) ? POS_ONE : NEG_ONE;
    always@(posedge clk or negedge rst_n) begin
        if (rst_n==1'b0) begin
            wcnt <=`U_DLY {CNT_BW{1'b0}};
        end else begin
            if (wcnt_ce==1'b1)
                wcnt <=`U_DLY wcnt + wcnt_inc;
            else
                ;
        end
    end

    // wptr_d[RDPL_NUM], rptr and wcnt always keep in touch with each other for high reliability
    // they can recover to normal state automatically after any abnormal situation
    if (DEPTH==DEPTH_EXP) begin:PWR2
        assign wptr = rptr + wcnt;
    end else begin:NPWR2
        wire    [CNT_BW:0]  sum;

        assign sum  = rptr + wcnt;

        assign wptr = (sum<{1'b0, DEPTH_V}) ?  sum[0+:CNT_BW]           :
                                              (sum[0+:CNT_BW] - DEPTH_V);
    end

    always@(*) begin
        rvld_d[0] = raw_fifo_ren;
        wptr_d[0] = wptr;
    end

    assign wptr_chg = (wptr!=wptr_d[RDPL_NUM]) ? 1'b1 : 1'b0;
    assign rdpl_ce  = (|rvld_d) | wptr_chg;

    genvar g;
    for (g=0; g<RDPL_NUM; g=g+1) begin:G_RDPL
        
        always@(posedge clk or negedge rst_n) begin
            if (rst_n==1'b0) begin
                rvld_d[g+1] <=`U_DLY 1'b0;
                wptr_d[g+1] <=`U_DLY {CNT_BW{1'b0}};
            end else begin
                if (rdpl_ce==1'b1) begin
                    rvld_d[g+1] <=`U_DLY rvld_d[g];
                    wptr_d[g+1] <=`U_DLY wptr_d[g];
                end else
                    ;
            end
        end

    end

    assign rptr_c = (rptr>=DEPTH_DEC) ? ZERO : (rptr + 1'd1);
    always@(posedge clk or negedge rst_n) begin
        if (rst_n==1'b0) begin
            rptr <=`U_DLY {CNT_BW{1'b0}};
        end else begin
            if (cache_ren==1'b1)
                rptr <=`U_DLY rptr_c;
            else
                ;
        end
    end

    tiny_mem #(
        .DEPTH                          (DEPTH                          ),
        .DATA_BW                        (DATA_BW                        ),
        .OUT_REG_EN                     (1'b0                           ) 	// read data is combinatioanl logic
    ) u_tiny_mem ( 
        .rst_wr_n                       (rst_n                          ),
        .clk_wr                         (clk                            ),
        .mem_wen                        (rvld_d[RDPL_NUM]               ),
        .mem_waddr                      (wptr_d[RDPL_NUM][PTR_BW-1:0]   ),
        .mem_wdata                      (mem_rdata                      ),

        .rst_rd_n                       (rst_n                          ),
        .clk_rd                         (clk                            ),
        .mem_ren                        (cache_ren                      ),
        .mem_raddr                      (rptr[PTR_BW-1:0]               ),
        .mem_rvld                       (mem_rvld_nc                    ),
        .mem_rdata                      (zrl_fifo_rdata                 )
    );

    assign zrl_fifo_empty = (wptr_d[RDPL_NUM]!=rptr) ? 1'b1 : 1'b0;

    assign mem_rdpl_ce    = {RDPL_NUM{rdpl_ce}};
    assign prefetch_cnt   = wcnt;

end else begin:G_VOID
`ifdef PARAMETER_NULL_CHECK  
    // if 'ARCH_MODE' wrong, force compiler to error for attention.
    arch_mode_null u_null();
`endif
end endgenerate

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off


// synopsys translate_on
`endif

endmodule
